package com.jvyou.app.ds.service.impl;

import cn.dev33.satoken.secure.BCrypt;
import cn.dev33.satoken.stp.StpUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.jvyou.app.common.constants.BaseConstants;
import com.jvyou.app.common.constants.RedisKey;
import com.jvyou.app.common.domain.dto.LoginDto;
import com.jvyou.app.common.domain.dto.ResetPasswordDto;
import com.jvyou.app.common.domain.dto.SMSLoginDto;
import com.jvyou.app.common.domain.entity.User;
import com.jvyou.app.common.domain.model.LoginUser;
import com.jvyou.app.common.domain.model.Result;
import com.jvyou.app.common.domain.vo.LoginVo;
import com.jvyou.app.common.domain.vo.SMSTokenVo;
import com.jvyou.app.common.enums.HttpCodeEnum;
import com.jvyou.app.common.exception.AppException;
import com.jvyou.app.common.utils.bean.BeanCopyUtils;
import com.jvyou.app.common.utils.random.RandomCodeUtil;
import com.jvyou.app.common.utils.value.ValueUtil;
import com.jvyou.app.ds.config.AppConfig;
import com.jvyou.app.ds.mapper.MenuMapper;
import com.jvyou.app.ds.mapper.RoleMapper;
import com.jvyou.app.ds.mapper.UserMapper;
import com.jvyou.app.ds.service.AuthService;
import com.jvyou.app.ds.utils.login.LoginContext;
import com.jvyou.app.ds.utils.redis.RedisCache;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * @author 橘柚
 * @since 2025/3/3 10:51
 */
@Service("authService")
@AllArgsConstructor
public class AuthServiceImpl implements AuthService {

    private final UserMapper userMapper;
    private final RoleMapper roleMapper;
    private final MenuMapper menuMapper;
    private final RedisCache redisCache;
    private final AppConfig appConfig;

    @Override
    public Result<LoginVo> doLogin(LoginDto loginDto) {
        // 1. 根据用户名查询用户
        LambdaUpdateWrapper<User> userWrapper = new LambdaUpdateWrapper<>();
        userWrapper.eq(User::getUsername, loginDto.getUsername());
        User user = userMapper.selectOne(userWrapper);
        // 2. 判断用户是否存在
        Optional.ofNullable(user).orElseThrow(() -> new AppException(HttpCodeEnum.USER_NOT_EXIST));
        if (Objects.equals(user.getStatus(), BaseConstants.LOCK)) {
            throw new AppException(HttpCodeEnum.USER_LOCKED);
        }
        // 3. 校验密码
        boolean pwdRight = BCrypt.checkpw(loginDto.getPassword(), user.getPassword());
        if (!pwdRight) {
            throw new AppException(HttpCodeEnum.PASSWORD_NOT_RIGHT);
        }
        // 4. 登录
        return Result.success(handleLogin(user));
    }

    @Override
    public Result<LoginVo> doSMSLogin(SMSLoginDto smsLoginDto) {
        // 1. 校验验证码
        String codeKey = RedisKey.SMS_CODE_KEY + smsLoginDto.getPhone();
        handleVerifyCode(smsLoginDto.getPhone(), smsLoginDto.getCode());
        // 2. 删除验证码
        redisCache.deleteObject(codeKey);
        // 3. 获取用户信息
        LambdaQueryWrapper<User> userWrapper = new LambdaQueryWrapper<>();
        userWrapper.eq(User::getPhone, smsLoginDto.getPhone());
        User user = userMapper.selectOne(userWrapper);
        if (user == null) {
            return Result.error(HttpCodeEnum.USER_NOT_EXIST);
        }
        if (user.getStatus().equals(BaseConstants.LOCK)) {
            throw new AppException(HttpCodeEnum.USER_LOCKED);
        }
        // 4. 登录
        return Result.success(handleLogin(user));
    }

    @Override
    public SMSTokenVo verifyCode(String phone, String code, String operation) {
        // 1. 验证验证码
        handleVerifyCode(phone, code);
        // 2. 生成临时的验证码
        String tokenKey = RedisKey.SMS_OP_TOKEN + operation + ":" + phone;
        String operationToken = UUID.randomUUID().toString();
        redisCache.setCacheObject(tokenKey, operationToken, 5, TimeUnit.MINUTES);
        return new SMSTokenVo(operationToken);
    }

    @Override
    public Boolean resetPassword(ResetPasswordDto resetPasswordDto) {
        Long userId = LoginContext.getUserId();
        User user = userMapper.selectById(userId);
        // 1. 校验验证码
        boolean checkpw = BCrypt.checkpw(resetPasswordDto.getOldPassword(), user.getPassword());
        if (!checkpw) {
            throw new AppException(HttpCodeEnum.PASSWORD_NOT_RIGHT, "原密码错误");
        }
        // 2. 修改用户密码
        String newPwd = BCrypt.hashpw(resetPasswordDto.getNewPassword());
        LambdaUpdateWrapper<User> userWrapper = new LambdaUpdateWrapper<>();
        userWrapper.set(User::getPassword, newPwd);
        int update = userMapper.update(null, userWrapper);
        return update > 0;
    }


    /**
     * 校验验证码
     *
     * @param phone 手机号
     * @param code  验证码
     */
    private void handleVerifyCode(String phone, String code) {
        String codeKey = RedisKey.SMS_CODE_KEY + phone;
        String cacheCode = redisCache.getCacheObject(codeKey);
        if (ValueUtil.isEmpty(cacheCode)) {
            throw new AppException(HttpCodeEnum.SMS_CODE_EXPIRE, "验证码不存在，请重新发送验证码");
        }
        if (!code.equals(cacheCode)) {
            throw new AppException(HttpCodeEnum.SMS_CODE_ERROR);
        }

    }

    private LoginVo handleLogin(User user) {
        StpUtil.login(user.getId());
        String token = StpUtil.getTokenValue();
        LoginUser loginUser = BeanCopyUtils.copyBean(user, LoginUser.class);
        List<String> roleList = roleMapper.getRoleByUserId(user.getId());
        List<String> permissionList = menuMapper.getPermissionByUserId(user.getId());
        loginUser.setPermissions(permissionList);
        loginUser.setRoles(roleList);
        // 缓存用户信息
        StpUtil.getSession().set(RedisKey.LOGIN_USER_KEY + user.getId(), loginUser);
        return new LoginVo(token);
    }

    @Override
    public Result<LoginUser> currentUser() {
        LoginUser loginUser = (LoginUser) StpUtil.getSession().get(RedisKey.LOGIN_USER_KEY + StpUtil.getLoginId());
        return Result.success(loginUser);
    }

    @Override
    public Boolean sendSMSCode(String phone) {
        // 1. 限制 60 秒内只能发送一次
        String limitKey = RedisKey.SMS_LIMIT_KEY + phone;
        if (redisCache.getCacheObject(limitKey) != null) {
            throw new AppException(HttpCodeEnum.SMS_LIMIT);
        }
        // 2. 生成手机验证码
        String code = RandomCodeUtil.generate6DigitCode(true);
        String codeKey = RedisKey.SMS_CODE_KEY + phone;
        // 缓存验证码信息
        redisCache.setCacheObject(codeKey, code, appConfig.getSms().getCodeExpireTime(), TimeUnit.MINUTES);
        // 验证码发送时间间隔
        redisCache.setCacheObject(limitKey, "1", appConfig.getSms().getCodeIntervalTime(), TimeUnit.SECONDS);
        return true;
    }
}
